#include "onebutton.h"

//int max(int a, int b)
//{
//    if(a >= b)
//        return a;
//    else
//        return b;
//}
void ob_init(OneBtn_t *pin, boolean activeLow , bool pullupActive)
{
    pin->_debounceTicks = 30;
    pin->_clickTicks = 100;
    pin->_pressTicks = 1000;
    pin->_duringLongPressFuncInterval = 200;
    pin->_clickFunc = NULL;
    pin->_doubleClickFunc = NULL;
    pin->_multiClickFunc = NULL;
    pin->_longPressStartFunc = NULL;
    pin->_longPressStopFunc = NULL;
    pin->_duringLongPressFunc = NULL;

    pin->_maxClicks = 1;
    pin->_state = OCS_INIT;
    pin->_lastState = OCS_INIT;
    
    // OneButton();

    if (activeLow) {
        // the button connects the input pin to GND when pressed.
        pin->_buttonPressed = LOW;

    } else {
        // the button connects the input pin to VCC when pressed.
        pin->_buttonPressed = HIGH;
    } // if

    if (pullupActive) {
        // use the given pin as input and activate internal PULLUP resistor.
        //    pinMode(pin, INPUT_PULLUP);
        pin->p->mode(INPUT_PU);
    } else {
        // use the given pin as input
        //    pinMode(pin, INPUT);
        pin->p->mode(INPUT_PD);
    }

    

}

void ob_setDebounceTicks(OneBtn_t *pin, int ticks)
{
  pin->_debounceTicks = ticks;
}

// explicitly set the number of millisec that have to pass by before a click is detected.
void ob_setClickTicks(OneBtn_t *pin,  int ticks)
{
  pin->_clickTicks = ticks;
} // setClickTicks


// explicitly set the number of millisec that have to pass by before a long button press is detected.
void ob_setPressTicks(OneBtn_t *pin,  int ticks)
{
  pin->_pressTicks = ticks;
} // setPressTicks


// save function for click event
void ob_attachClick(OneBtn_t *pin, callbackFunction newFunction)
{
  pin->_clickFunc = newFunction;
} // attachClick

// save function for doubleClick event
void ob_attachDoubleClick(OneBtn_t *pin, callbackFunction newFunction)
{
  pin->_doubleClickFunc = newFunction;
  pin->_maxClicks = max(pin->_maxClicks, 2);
} // attachDoubleClick
// save function for multiClick event
void ob_attachMultiClick(OneBtn_t *pin, callbackFunction newFunction)
{
  pin->_multiClickFunc = newFunction;
  pin->_maxClicks = max(pin->_maxClicks, 100);
} // attachMultiClick
// save function for longPressStart event
void ob_attachLongPressStart(OneBtn_t *pin, callbackFunction newFunction)
{
  pin->_longPressStartFunc = newFunction;
} // attachLongPressStart

// save function for longPressStop event
void ob_attachLongPressStop(OneBtn_t *pin, callbackFunction newFunction)
{
  pin->_longPressStopFunc = newFunction;
} // attachLongPressStop

// save function for during longPress event
void ob_attachDuringLongPress(OneBtn_t *pin, callbackFunction newFunction)
{
  pin->_duringLongPressFunc = newFunction;
} // attachDuringLongPress
void ob_reset(OneBtn_t *pin)
{
  pin->_state = OCS_INIT;
  pin->_lastState = OCS_INIT;
  pin->_nClicks = 0;
  pin->_startTime = 0;
}
// ShaggyDog ---- return number of clicks in any case: single or multiple clicks
int ob_getNumberClicks(OneBtn_t *pin)
{
  return pin->_nClicks;
}


/**
 * @brief Check input of the configured pin and then advance the finite state
 * machine (FSM).
 */
void ob_tick(OneBtn_t *pin)
{
    ob_tick_l(pin, pin->p->read() == pin->_buttonPressed);
}
/**
 *  @brief Advance to a new state and save the last one to come back in cas of bouncing detection.
 */
void ob_newState(OneBtn_t *pin, stateMachine_t nextState)
{
  pin->_lastState = pin->_state;
  pin->_state = nextState;
} // _newState()

/**
 * @brief Run the finite state machine (FSM) using the given level.
 */
void ob_tick_l(OneBtn_t *pin, bool activeLevel)
{
  unsigned long now = millis(); // current (relative) time in msecs.
  unsigned long waitTime = (now - pin->_startTime);

  // Implementation of the state machine
  switch (pin->_state) {
  case OCS_INIT:
    // waiting for level to become active.
    if (activeLevel) {
      ob_newState(pin, OCS_DOWN);
      pin->_startTime = now; // remember starting time
      pin->_nClicks = 0;
    } // if
    break;

  case OCS_DOWN:
    // waiting for level to become inactive.

    if ((!activeLevel) && (waitTime < pin->_debounceTicks)) {
      // button was released to quickly so I assume some bouncing.
      ob_newState(pin, pin->_lastState);

    } else if (!activeLevel) {
      ob_newState(pin, OCS_UP);
      pin->_startTime = now; // remember starting time

    } else if ((activeLevel) && (waitTime > pin->_pressTicks)) {
      if (pin->_longPressStartFunc) pin->_longPressStartFunc();
      ob_newState(pin, OCS_PRESS);
    } // if
    break;

  case OCS_UP:
    // level is inactive

    if ((activeLevel) && (waitTime < pin->_debounceTicks)) {
      // button was pressed to quickly so I assume some bouncing.
      ob_newState(pin, pin->_lastState); // go back

    } else if (waitTime >= pin->_debounceTicks) {
      // count as a short button down
      pin->_nClicks++;
      ob_newState(pin, OCS_COUNT);
    } // if
    break;

  case OCS_COUNT:
    // dobounce time is over, count clicks

    if (activeLevel) {
      // button is down again
      ob_newState(pin, OCS_DOWN);
      pin->_startTime = now; // remember starting time

    } else if ((waitTime > pin->_clickTicks) || (pin->_nClicks == pin->_maxClicks)) {
      // now we know how many clicks have been made.

      if (pin->_nClicks == 1) {
        // this was 1 click only.
        if (pin->_clickFunc) pin->_clickFunc();

      } else if (pin->_nClicks == 2) {
        // this was a 2 click sequence.
        if (pin->_doubleClickFunc) pin->_doubleClickFunc();

      } else {
        // this was a multi click sequence.
        if (pin->_multiClickFunc) pin->_multiClickFunc();
      } // if

      ob_reset(pin);
    } // if
    break;

  case OCS_PRESS:
    // waiting for menu pin being release after long press.

    if (!activeLevel) {
      ob_newState(pin, OCS_PRESSEND);
      pin->_startTime = now;

    } else {
      // still the button is pressed
        if(millis() - pin->_duringLongPressFuncLastTime >= pin->_duringLongPressFuncInterval)
        {
            pin->_duringLongPressFuncLastTime = millis();
            if (pin->_duringLongPressFunc) pin->_duringLongPressFunc();
        }
    } // if
    break;

  case OCS_PRESSEND:
    // button was released.

    if ((activeLevel) && (waitTime < pin->_debounceTicks)) {
      // button was released to quickly so I assume some bouncing.
      ob_newState(pin, pin->_lastState); // go back

    } else if (waitTime >= pin->_debounceTicks) {
      if (pin->_longPressStopFunc) pin->_longPressStopFunc();
      ob_reset(pin);
    }
    break;

  default:
    // unknown state detected -> reset state machine
    ob_newState(pin, OCS_INIT);
    break;
  } // if
}


